import tkinter as tk
from tkinter import ttk, filedialog, messagebox, font, colorchooser, simpledialog
import os
import json
import shutil
from datetime import datetime, timedelta
import markdown
from tkinter.scrolledtext import ScrolledText
import webbrowser
import tempfile
from cryptography.fernet import Fernet
import base64
import hashlib

# ==================== 加密工具类 ====================
def derive_key_from_password(password: str, salt: bytes) -> bytes:
    """从密码生成密钥（PBKDF2）"""
    key = hashlib.pbkdf2_hmac('sha256', password.encode(), salt, 100000, dklen=32)
    return base64.urlsafe_b64encode(key)

def encrypt_data(data: str, password: str) -> str:
    """加密数据"""
    salt = os.urandom(16)
    key = derive_key_from_password(password, salt)
    f = Fernet(key)
    token = f.encrypt(data.encode())
    return base64.urlsafe_b64encode(salt + token).decode()

def decrypt_data(data: str, password: str) -> str:
    """解密数据"""
    try:
        data_bin = base64.urlsafe_b64decode(data)
        salt, token = data_bin[:16], data_bin[16:]
        key = derive_key_from_password(password, salt)
        f = Fernet(key)
        decrypted = f.decrypt(token)
        return decrypted.decode()
    except Exception:
        raise ValueError("密码错误或数据损坏")

# ==================== 主应用类 ====================
class NoteApp:
    def __init__(self, root):
        self.root = root
        self.root.title("笔记应用")
        self.root.geometry("1200x700")

        # 配置路径
        self.app_dir = os.path.dirname(os.path.abspath(__file__)) if '__file__' in globals() else '.'
        self.config_file = os.path.join(self.app_dir, 'config.json')
        self.load_config()

        # 创建必要目录
        os.makedirs(self.notes_dir, exist_ok=True)
        os.makedirs(self.backup_dir, exist_ok=True)
        os.makedirs(self.trash_dir, exist_ok=True)

        # 安全相关
        self.master_password = None
        self.fernet = None
        self.require_master_password()

        # 应用状态
        self.current_file = None
        self.is_modified = False
        self.clipboard_history = []
        self.undo_stack = []
        self.redo_stack = []
        self.max_undo_steps = 100
        self.recent_files = []
        self.column_select_mode = False
        self.selection_start = None

        # 笔记树相关
        self.note_tree = None
        self.sort_mode = "modified_desc"  # created_asc, created_desc, modified_asc, modified_desc, title, custom
        self.trash_meta_file = os.path.join(self.trash_dir, ".trash.json")
        self.trash_meta = self.load_trash_meta()

        # 创建界面
        self.create_menu()
        self.create_toolbar()
        self.create_main_layout()
        self.create_status_bar()

        # 加载数据
        self.load_recent_files()
        self.update_recent_files_menu()
        self.build_note_tree()

        # 绑定事件
        self.bind_events()

        # 自动保存
        self.auto_save_job = None
        self.start_auto_save()

        # 清理过期回收站
        self.cleanup_old_trash()

    def load_config(self):
        """加载配置"""
        default_config = {
            "notes_dir": os.path.join(self.app_dir, "notes"),
            "backup_interval": "daily",
            "encrypt_notes": True
        }
        if os.path.exists(self.config_file):
            try:
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                default_config.update(config)
            except:
                pass
        self.config = default_config
        self.notes_dir = self.config["notes_dir"]
        self.backup_dir = os.path.join(self.app_dir, "backups")
        self.trash_dir = os.path.join(self.app_dir, "trash")

    def save_config(self):
        """保存配置"""
        with open(self.config_file, 'w', encoding='utf-8') as f:
            json.dump(self.config, f, ensure_ascii=False, indent=2)

    def require_master_password(self):
        """获取主密码"""
        password_file = os.path.join(self.app_dir, 'master.key')
        if os.path.exists(password_file):
            # 已设置密码，要求输入
            for _ in range(3):
                pwd = simpledialog.askstring("密码验证", "请输入主密码：", show='*')
                if not pwd:
                    exit()
                try:
                    with open(password_file, 'rb') as f:
                        key_data = f.read()
                    # 验证密码：尝试解密测试数据
                    test_enc = encrypt_data("test", pwd)
                    test_dec = decrypt_data(test_enc, pwd)
                    if test_dec == "test":
                        self.master_password = pwd
                        return
                except:
                    messagebox.showerror("错误", "密码错误，请重试")
            exit()
        else:
            # 首次设置
            while True:
                pwd1 = simpledialog.askstring("设置密码", "设置主密码：", show='*')
                if not pwd1:
                    exit()
                pwd2 = simpledialog.askstring("确认密码", "再次输入：", show='*')
                if pwd1 == pwd2:
                    # 保存盐和验证用密钥（不存明文密码）
                    salt = os.urandom(16)
                    with open(password_file, 'wb') as f:
                        f.write(salt)  # 只存盐，用于后续派生密钥
                    self.master_password = pwd1
                    return
                else:
                    messagebox.showerror("错误", "两次密码不一致")

    def create_main_layout(self):
        """创建主布局：左侧树状目录 + 右侧编辑区"""
        main_paned = ttk.PanedWindow(self.root, orient=tk.HORIZONTAL)
        main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        # 左侧面板
        left_frame = ttk.Frame(main_paned)
        main_paned.add(left_frame, weight=1)

        # 标题栏
        top_bar = ttk.Frame(left_frame)
        top_bar.pack(fill=tk.X, pady=2)

        ttk.Button(top_bar, text="刷新", command=self.refresh_tree).pack(side=tk.LEFT, padx=2)
        ttk.Button(top_bar, text="新建笔记", command=self.new_note_from_tree).pack(side=tk.LEFT, padx=2)
        ttk.Button(top_bar, text="新建文件夹", command=self.new_folder).pack(side=tk.LEFT, padx=2)

        # 排序按钮
        sort_frame = ttk.LabelFrame(left_frame, text="排序")
        sort_frame.pack(fill=tk.X, pady=2)
        sort_btns = [
            ("按修改时间↓", "modified_desc"),
            ("按创建时间↓", "created_desc"),
            ("按标题", "title"),
        ]
        for label, mode in sort_btns:
            ttk.Button(sort_frame, text=label, width=12,
                      command=lambda m=mode: self.set_sort_mode(m)).pack(side=tk.LEFT, padx=2)

        # 笔记树
        tree_frame = ttk.Frame(left_frame)
        tree_frame.pack(fill=tk.BOTH, expand=True)

        self.note_tree = ttk.Treeview(tree_frame, columns=("path",), show="tree", selectmode="extended")
        self.note_tree.heading("#0", text="笔记与文件夹")
        self.note_tree.column("#0", width=200)
        self.note_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        tree_scroll = ttk.Scrollbar(tree_frame, orient="vertical", command=self.note_tree.yview)
        tree_scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.note_tree.configure(yscrollcommand=tree_scroll.set)

        # 启用拖拽排序
        self.note_tree.bind("<ButtonPress-1>", self.on_tree_click)
        self.note_tree.bind("<B1-Motion>", self.on_tree_drag)
        self.note_tree.bind("<ButtonRelease-1>", self.on_tree_drop)
        self.note_tree.bind("<Double-1>", self.on_tree_double_click)
        self.note_tree.bind("<Button-3>", self.show_context_menu)

        # 右侧编辑区
        right_frame = ttk.Frame(main_paned)
        main_paned.add(right_frame, weight=3)

        self.create_text_area_in_frame(right_frame)

    def create_text_area_in_frame(self, parent):
        """在指定父容器中创建文本区域"""
        text_frame = ttk.Frame(parent)
        text_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.text_area = ScrolledText(
            text_frame,
            wrap=tk.WORD,
            undo=True,
            font=("微软雅黑", 12)
        )
        self.text_area.pack(fill=tk.BOTH, expand=True)

        # 定义文本格式标签
        self.text_area.tag_configure("bold", font=("微软雅黑", 12, "bold"))
        self.text_area.tag_configure("italic", font=("微软雅黑", 12, "italic"))
        self.text_area.tag_configure("underline", underline=True)
        self.text_area.tag_configure("strikethrough", overstrike=True)
        self.text_area.tag_configure("highlight", background="#FFFF00")  # 黄色高亮

    def build_note_tree(self):
        """构建笔记树状结构"""
        self.note_tree.delete(*self.note_tree.get_children())
        self.note_tree.insert("", "end", "root", text="笔记", open=True)
        self.load_directory_to_tree(self.notes_dir, "root")
        self.apply_sorting()

    def load_directory_to_tree(self, path, parent_id):
        """递归加载目录到树"""
        try:
            items = os.listdir(path)
            dirs = [d for d in items if os.path.isdir(os.path.join(path, d))]
            files = [f for f in items if os.path.isfile(os.path.join(path, f)) and f.endswith(('.txt', '.md'))]

            for d in sorted(dirs):
                dir_path = os.path.join(path, d)
                display_name = d
                node_id = self.note_tree.insert(parent_id, "end", text=display_name, values=(dir_path,))
                self.load_directory_to_tree(dir_path, node_id)

            for f in files:
                file_path = os.path.join(path, f)
                display_name = os.path.splitext(f)[0]
                node_id = self.note_tree.insert(parent_id, "end", text=display_name, values=(file_path,))
        except Exception as e:
            messagebox.showerror("错误", f"读取目录失败: {e}")

    def set_sort_mode(self, mode):
        """设置排序模式并重新排序"""
        self.sort_mode = mode
        self.apply_sorting()

    def apply_sorting(self):
        """根据当前排序模式对树节点排序"""
        def sort_key(item_id):
            text = self.note_tree.item(item_id, "text")
            values = self.note_tree.item(item_id, "values")
            path = values[0] if values else ""
            if os.path.isdir(path):
                return (0, text.lower())  # 文件夹优先
            else:
                try:
                    stat = os.stat(path)
                    ctime = stat.st_ctime
                    mtime = stat.st_mtime
                    if self.sort_mode == "created_asc":
                        return (1, ctime)
                    elif self.sort_mode == "created_desc":
                        return (1, -ctime)
                    elif self.sort_mode == "modified_asc":
                        return (1, mtime)
                    elif self.sort_mode == "modified_desc":
                        return (1, -mtime)
                    elif self.sort_mode == "title":
                        return (1, text.lower())
                except:
                    return (1, text.lower())
            return (0, text.lower())

        root_children = self.note_tree.get_children("root")
        sorted_children = sorted(root_children, key=sort_key)
        for i, child in enumerate(sorted_children):
            self.note_tree.move(child, "root", i)

    def on_tree_click(self, event):
        self.drag_data = {"item": None, "y": 0}
        item = self.note_tree.identify_row(event.y)
        if item:
            self.drag_data["item"] = item
            self.drag_data["y"] = event.y

    def on_tree_drag(self, event):
        if hasattr(self, 'drag_data') and self.drag_data["item"]:
            y = event.y
            item = self.drag_data["item"]
            self.note_tree.move(item, self.note_tree.parent(item), "end")

    def on_tree_drop(self, event):
        if hasattr(self, 'drag_data') and self.drag_data["item"]:
            self.drag_data = {}

    def on_tree_double_click(self, event):
        item = self.note_tree.focus()
        values = self.note_tree.item(item, "values")
        if values and os.path.isfile(values[0]):
            self.open_file_by_path(values[0])

    def open_file_by_path(self, file_path):
        """通过路径打开文件（支持加密）"""
        if self.check_save_before_action():
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                if file_path.endswith('.enc'):
                    content = decrypt_data(content, self.master_password)
                self.text_area.delete(1.0, tk.END)
                self.text_area.insert(1.0, content)
                self.current_file = file_path
                self.is_modified = False
                self.status_label.config(text=f"已打开: {os.path.basename(file_path)}")
                self.add_to_recent_files(file_path)
                self.update_recent_files_menu()
            except Exception as e:
                messagebox.showerror("错误", f"打开失败: {str(e)}")

    def show_context_menu(self, event):
        """右键菜单"""
        item = self.note_tree.identify_row(event.y)
        if not item:
            return
        selected_items = self.note_tree.selection()
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label="打开", command=lambda: self.on_tree_double_click(None))
        menu.add_command(label="重命名", command=self.rename_item)
        menu.add_command(label="删除", command=self.delete_items)
        menu.add_command(label="移动到...", command=self.move_items)
        menu.add_separator()
        menu.add_command(label="批量添加标签", command=self.add_tags)
        menu.post(event.x_root, event.y_root)

    def rename_item(self):
        item = self.note_tree.focus()
        old_path = self.note_tree.item(item, "values")[0]
        new_name = simpledialog.askstring("重命名", "新名称:", initialvalue=self.note_tree.item(item, "text"))
        if not new_name:
            return
        new_path = os.path.join(os.path.dirname(old_path),
                                new_name + (os.path.splitext(old_path)[1] if os.path.isfile(old_path) else ""))
        try:
            os.rename(old_path, new_path)
            self.note_tree.item(item, text=new_name, values=(new_path,))
        except Exception as e:
            messagebox.showerror("错误", f"重命名失败: {e}")

    def delete_items(self):
        selected = self.note_tree.selection()
        for item in selected:
            path = self.note_tree.item(item, "values")[0]
            if os.path.exists(path):
                trash_path = os.path.join(self.trash_dir, os.path.basename(path))
                counter = 1
                while os.path.exists(trash_path):
                    name, ext = os.path.splitext(os.path.basename(path))
                    trash_path = os.path.join(self.trash_dir, f"{name}_{counter}{ext}")
                    counter += 1
                shutil.move(path, trash_path)
                self.trash_meta[os.path.basename(trash_path)] = datetime.now().isoformat()
        self.save_trash_meta()
        self.refresh_tree()
        messagebox.showinfo("提示", "已移至回收站")

    def move_items(self):
        target = filedialog.askdirectory(title="选择目标文件夹", initialdir=self.notes_dir)
        if not target:
            return
        for item in self.note_tree.selection():
            path = self.note_tree.item(item, "values")[0]
            if os.path.isfile(path):
                shutil.move(path, os.path.join(target, os.path.basename(path)))
        self.refresh_tree()

    def add_tags(self):
        tag = simpledialog.askstring("标签", "输入标签（逗号分隔）:")
        if tag:
            messagebox.showinfo("功能提示", f"标签功能待实现（当前为演示）: {tag}")

    def refresh_tree(self):
        self.build_note_tree()

    def new_note_from_tree(self):
        parent = self.note_tree.focus() or "root"
        parent_path = self.notes_dir
        if parent != "root":
            parent_path = self.note_tree.item(parent, "values")[0]
            if os.path.isfile(parent_path):
                parent_path = os.path.dirname(parent_path)

        title = simpledialog.askstring("新建笔记", "笔记标题:")
        if not title:
            return
        file_path = os.path.join(parent_path, f"{title}.md")
        if os.path.exists(file_path):
            file_path = os.path.join(parent_path, f"{title}_new.md")
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(f"# {title}\n\n")
        self.refresh_tree()
        self.open_file_by_path(file_path)

    def new_folder(self):
        parent = self.note_tree.focus() or "root"
        parent_path = self.notes_dir
        if parent != "root":
            parent_path = self.note_tree.item(parent, "values")[0]
            if os.path.isfile(parent_path):
                parent_path = os.path.dirname(parent_path)
        name = simpledialog.askstring("新建文件夹", "文件夹名:")
        if name:
            os.makedirs(os.path.join(parent_path, name), exist_ok=True)
            self.refresh_tree()

    def load_trash_meta(self):
        if os.path.exists(self.trash_meta_file):
            with open(self.trash_meta_file, 'r', encoding='utf-8') as f:
                return json.load(f)
        return {}

    def save_trash_meta(self):
        with open(self.trash_meta_file, 'w', encoding='utf-8') as f:
            json.dump(self.trash_meta, f, ensure_ascii=False, indent=2)

    def cleanup_old_trash(self):
        now = datetime.now()
        to_delete = []
        for filename, del_time in self.trash_meta.items():
            try:
                del_dt = datetime.fromisoformat(del_time)
                if now - del_dt > timedelta(days=30):
                    file_path = os.path.join(self.trash_dir, filename)
                    if os.path.exists(file_path):
                        os.remove(file_path)
                    to_delete.append(filename)
            except:
                continue
        for fname in to_delete:
            del self.trash_meta[fname]
        self.save_trash_meta()

    # ==================== 格式化功能 ====================
    def change_text_color(self):
        """改变选中文本的颜色"""
        try:
            color = colorchooser.askcolor(title="选择文字颜色")
            if color[1]:
                start = self.text_area.index(tk.SEL_FIRST)
                end = self.text_area.index(tk.SEL_LAST)
                tag_name = f"color_{color[1].replace('#', '')}"
                self.text_area.tag_configure(tag_name, foreground=color[1])
                self.text_area.tag_add(tag_name, start, end)
        except tk.TclError:
            pass  # 没有选中文本

    def change_bg_color(self):
        """改变选中文本的背景颜色"""
        try:
            color = colorchooser.askcolor(title="选择背景颜色")
            if color[1]:
                start = self.text_area.index(tk.SEL_FIRST)
                end = self.text_area.index(tk.SEL_LAST)
                tag_name = f"bg_{color[1].replace('#', '')}"
                self.text_area.tag_configure(tag_name, background=color[1])
                self.text_area.tag_add(tag_name, start, end)
        except tk.TclError:
            pass  # 没有选中文本

    def change_font(self):
        """改变选中文本的字体"""
        try:
            # 获取当前选中文本的字体信息
            current_font = font.Font(font=self.text_area["font"])
            current_family = current_font.actual("family")
            current_size = current_font.actual("size")
            
            # 创建字体选择对话框
            font_win = tk.Toplevel(self.root)
            font_win.title("选择字体")
            font_win.geometry("400x200")
            font_win.transient(self.root)
            font_win.grab_set()
            
            # 字体名称
            ttk.Label(font_win, text="字体:").grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
            font_families = sorted(font.families())
            font_var = tk.StringVar(value=current_family)
            font_combo = ttk.Combobox(font_win, textvariable=font_var, values=font_families, width=30)
            font_combo.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)
            
            # 字号
            ttk.Label(font_win, text="字号:").grid(row=1, column=0, padx=10, pady=5, sticky=tk.W)
            size_var = tk.StringVar(value=str(current_size))
            size_combo = ttk.Combobox(font_win, textvariable=size_var, 
                                     values=[8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72], 
                                     width=5)
            size_combo.grid(row=1, column=1, padx=10, pady=5, sticky=tk.W)
            
            # 确定按钮
            def apply_font():
                family = font_var.get()
                size = size_var.get()
                try:
                    size = int(size)
                    if size < 6 or size > 144:
                        messagebox.showerror("错误", "字号必须在6-144之间")
                        return
                    
                    start = self.text_area.index(tk.SEL_FIRST)
                    end = self.text_area.index(tk.SEL_LAST)
                    tag_name = f"font_{family}_{size}"
                    
                    # 配置字体
                    self.text_area.tag_configure(tag_name, font=(family, size))
                    self.text_area.tag_add(tag_name, start, end)
                    
                    font_win.destroy()
                except ValueError:
                    messagebox.showerror("错误", "请输入有效的字号")
            
            ttk.Button(font_win, text="确定", command=apply_font).grid(row=2, column=0, columnspan=2, pady=15)
            font_win.wait_window()

        except tk.TclError:
            pass  # 没有选中文本

    def change_font_size(self, size):
        """改变选中文本的字号（通过预设值）"""
        try:
            start = self.text_area.index(tk.SEL_FIRST)
            end = self.text_area.index(tk.SEL_LAST)
            tag_name = f"size_{size}"
            self.text_area.tag_configure(tag_name, font=(self.text_area["font"].split()[0], size))
            self.text_area.tag_add(tag_name, start, end)
        except tk.TclError:
            pass  # 没有选中文本

    # ==================== 菜单与工具栏 ====================
    def create_menu(self):
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)

        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="新建笔记", command=self.new_note_from_tree)
        file_menu.add_command(label="打开", command=self.open_file)
        file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
        file_menu.add_command(label="另存为", command=self.save_as_file)
        file_menu.add_separator()
        file_menu.add_command(label="设置存储路径", command=self.set_notes_dir)
        file_menu.add_command(label="管理回收站", command=self.open_trash_manager)
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.exit_app)

        edit_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        edit_menu.add_command(label="撤销", command=self.undo)
        edit_menu.add_command(label="重做", command=self.redo)
        edit_menu.add_separator()
        edit_menu.add_command(label="剪切", command=self.cut)
        edit_menu.add_command(label="复制", command=self.copy)
        edit_menu.add_command(label="粘贴", command=self.paste)
        edit_menu.add_command(label="全选", command=self.select_all)

        search_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="查找", menu=search_menu)
        search_menu.add_command(label="查找", command=self.find_text)

        format_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="格式", menu=format_menu)
        format_menu.add_command(label="粗体", command=self.make_bold, accelerator="Ctrl+B")
        format_menu.add_command(label="斜体", command=self.make_italic, accelerator="Ctrl+I")
        format_menu.add_command(label="下划线", command=self.make_underline, accelerator="Ctrl+U")
        format_menu.add_separator()
        
        # 颜色菜单
        color_menu = tk.Menu(format_menu, tearoff=0)
        format_menu.add_cascade(label="颜色", menu=color_menu)
        color_menu.add_command(label="文字颜色", command=self.change_text_color)
        color_menu.add_command(label="背景颜色", command=self.change_bg_color)
        
        # 字体菜单
        font_menu = tk.Menu(format_menu, tearoff=0)
        format_menu.add_cascade(label="字体", menu=font_menu)
        font_menu.add_command(label="选择字体...", command=self.change_font)
        font_menu.add_separator()
        
        # 预设字号
        size_menu = tk.Menu(font_menu, tearoff=0)
        font_menu.add_cascade(label="字号", menu=size_menu)
        for size in [8, 10, 12, 14, 16, 18, 20, 24, 28, 36]:
            size_menu.add_command(label=f"{size}pt", command=lambda s=size: self.change_font_size(s))
        
        # 高亮菜单
        highlight_menu = tk.Menu(format_menu, tearoff=0)
        format_menu.add_cascade(label="高亮", menu=highlight_menu)
        highlight_menu.add_command(label="黄色高亮", command=lambda: self.apply_highlight("#FFFF00"))
        highlight_menu.add_command(label="绿色高亮", command=lambda: self.apply_highlight("#90EE90"))
        highlight_menu.add_command(label="蓝色高亮", command=lambda: self.apply_highlight("#ADD8E6"))
        highlight_menu.add_command(label="清除高亮", command=self.clear_highlight)

        view_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="视图", menu=view_menu)
        view_menu.add_command(label="预览HTML", command=self.preview_html)

    def apply_highlight(self, color):
        """应用文本高亮"""
        try:
            start = self.text_area.index(tk.SEL_FIRST)
            end = self.text_area.index(tk.SEL_LAST)
            tag_name = f"highlight_{color.replace('#', '')}"
            self.text_area.tag_configure(tag_name, background=color)
            self.text_area.tag_add(tag_name, start, end)
        except tk.TclError:
            pass  # 没有选中文本

    def clear_highlight(self):
        """清除选中文本的高亮"""
        try:
            start = self.text_area.index(tk.SEL_FIRST)
            end = self.text_area.index(tk.SEL_LAST)
            
            # 获取所有高亮标签
            highlight_tags = [tag for tag in self.text_area.tag_names() 
                             if tag.startswith("highlight_")]
            
            # 移除所有高亮标签
            for tag in highlight_tags:
                self.text_area.tag_remove(tag, start, end)
        except tk.TclError:
            pass  # 没有选中文本

    def set_notes_dir(self):
        new_dir = filedialog.askdirectory(title="选择笔记存储目录", initialdir=self.notes_dir)
        if new_dir:
            self.notes_dir = new_dir
            self.config["notes_dir"] = new_dir
            self.save_config()
            self.refresh_tree()

    def open_trash_manager(self):
        trash_win = tk.Toplevel(self.root)
        trash_win.title("回收站")
        trash_win.geometry("500x400")

        tree = ttk.Treeview(trash_win, columns=("delete_time",), show="tree headings")
        tree.heading("#0", text="文件名")
        tree.heading("delete_time", text="删除时间")
        tree.column("delete_time", width=150)

        for fname, del_time in self.trash_meta.items():
            dt = datetime.fromisoformat(del_time).strftime("%Y-%m-%d %H:%M")
            tree.insert("", "end", text=fname, values=(dt,))

        tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        def restore():
            selected = tree.selection()
            restored = 0
            for item in selected:
                fname = tree.item(item, "text")
                src = os.path.join(self.trash_dir, fname)
                dst = os.path.join(self.notes_dir, fname)
                if os.path.exists(src) and not os.path.exists(dst):
                    shutil.move(src, dst)
                    if fname in self.trash_meta:
                        del self.trash_meta[fname]
                    restored += 1
            self.save_trash_meta()
            tree.delete(*tree.get_children())
            self.refresh_tree()
            messagebox.showinfo("恢复", f"已恢复 {restored} 个文件")
            trash_win.destroy()

        ttk.Button(trash_win, text="恢复", command=restore).pack(pady=5)

    def create_toolbar(self):
        toolbar = ttk.Frame(self.root)
        toolbar.pack(side=tk.TOP, fill=tk.X, padx=2, pady=2)
        ttk.Button(toolbar, text="新建笔记", command=self.new_note_from_tree).pack(side=tk.LEFT, padx=2)
        ttk.Button(toolbar, text="保存", command=self.save_file).pack(side=tk.LEFT, padx=2)
        ttk.Button(toolbar, text="查找", command=self.find_text).pack(side=tk.LEFT, padx=2)
        
        # 添加格式化工具按钮
        ttk.Separator(toolbar, orient=tk.VERTICAL).pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=2)
        ttk.Button(toolbar, text="B", command=self.make_bold, width=2).pack(side=tk.LEFT, padx=1)
        ttk.Button(toolbar, text="I", command=self.make_italic, width=2).pack(side=tk.LEFT, padx=1)
        ttk.Button(toolbar, text="U", command=self.make_underline, width=2).pack(side=tk.LEFT, padx=1)
        ttk.Button(toolbar, text="A", command=self.change_text_color, width=2).pack(side=tk.LEFT, padx=1)
        ttk.Button(toolbar, text="H", command=lambda: self.apply_highlight("#FFFF00"), width=2).pack(side=tk.LEFT, padx=1)

    def create_status_bar(self):
        self.status_bar = ttk.Frame(self.root)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        self.status_label = ttk.Label(self.status_bar, text="就绪")
        self.status_label.pack(side=tk.LEFT, padx=5)
        self.position_label = ttk.Label(self.status_bar, text="行: 1, 列: 1")
        self.position_label.pack(side=tk.RIGHT, padx=5)

    def bind_events(self):
        self.root.bind('<Control-s>', lambda e: self.save_file())
        self.root.bind('<Control-z>', lambda e: self.undo())
        self.root.bind('<Control-y>', lambda e: self.redo())
        self.root.bind('<Control-a>', lambda e: self.select_all())
        self.root.bind('<Control-f>', lambda e: self.find_text())
        self.root.bind('<Control-b>', lambda e: self.make_bold())
        self.root.bind('<Control-i>', lambda e: self.make_italic())
        self.root.bind('<Control-u>', lambda e: self.make_underline())
        self.root.bind('<Control-h>', lambda e: self.apply_highlight("#FFFF00"))

        # 文本区事件
        self.text_area.bind('<Key>', self.on_text_change)
        self.text_area.bind('<Button-1>', self.on_text_click)
        self.text_area.bind('<B1-Motion>', self.on_text_drag)
        self.text_area.bind('<KeyRelease>', self.update_cursor_position)

        self.root.protocol("WM_DELETE_WINDOW", self.exit_app)

    def on_text_click(self, event=None):
        """文本点击：更新光标位置"""
        self.update_cursor_position()
        if self.column_select_mode:
            self.selection_start = self.text_area.index(f"@{event.x},{event.y}")

    def on_text_drag(self, event=None):
        """文本拖拽：支持列选择"""
        if self.column_select_mode and self.selection_start:
            pass  # 可扩展列选择逻辑

    def update_cursor_position(self, event=None):
        cursor_pos = self.text_area.index(tk.INSERT)
        line, col = cursor_pos.split('.')
        self.position_label.config(text=f"行: {line}, 列: {int(col)+1}")

    def on_text_change(self, event=None):
        self.is_modified = True
        self.update_cursor_position()
        self.save_state_for_undo()

    def save_state_for_undo(self):
        current_content = self.text_area.get(1.0, tk.END)
        if not self.undo_stack or self.undo_stack[-1] != current_content:
            self.undo_stack.append(current_content)
            if len(self.undo_stack) > self.max_undo_steps:
                self.undo_stack.pop(0)
            self.redo_stack.clear()

    def new_note(self):
        if self.check_save_before_action():
            self.text_area.delete(1.0, tk.END)
            self.current_file = None
            self.is_modified = False
            self.status_label.config(text="新建笔记")

    def open_file(self):
        if not self.check_save_before_action():
            return
        file_path = filedialog.askopenfilename(
            title="打开笔记",
            initialdir=self.notes_dir,
            filetypes=[("笔记文件", "*.md;*.txt"), ("所有文件", "*.*")]
        )
        if file_path:
            self.open_file_by_path(file_path)

    def save_file(self):
        if self.current_file:
            try:
                content = self.text_area.get(1.0, tk.END).strip()
                if self.config["encrypt_notes"] and not self.current_file.endswith('.enc'):
                    new_path = self.current_file + '.enc'
                    os.rename(self.current_file, new_path)
                    self.current_file = new_path
                encrypted = encrypt_data(content, self.master_password)
                with open(self.current_file, 'w', encoding='utf-8') as f:
                    f.write(encrypted)
                self.is_modified = False
                self.status_label.config(text=f"已保存: {os.path.basename(self.current_file)}")
                self.auto_backup()
                return True
            except Exception as e:
                messagebox.showerror("错误", f"保存失败: {e}")
                return False
        else:
            return self.save_as_file()

    def save_as_file(self):
        file_path = filedialog.asksaveasfilename(
            title="另存为",
            initialdir=self.notes_dir,
            defaultextension=".md",
            filetypes=[("Markdown文件", "*.md"), ("文本文件", "*.txt")]
        )
        if file_path:
            self.current_file = file_path
            return self.save_file()
        return False

    def auto_backup(self):
        now = datetime.now()
        backup_subdir = os.path.join(self.backup_dir, now.strftime("%Y-%m-%d"))
        os.makedirs(backup_subdir, exist_ok=True)
        if self.current_file and os.path.exists(self.current_file):
            try:
                shutil.copy2(self.current_file, backup_subdir)
            except:
                pass

    def check_save_before_action(self):
        if self.is_modified:
            result = messagebox.askyesnocancel("保存", "是否保存更改？")
            if result is True:
                return self.save_file()
            elif result is False:
                return True
            else:
                return False
        return True

    def exit_app(self):
        if self.check_save_before_action():
            if self.auto_save_job:
                self.root.after_cancel(self.auto_save_job)
            self.save_trash_meta()
            self.root.quit()

    def undo(self):
        if len(self.undo_stack) > 1:
            current_state = self.undo_stack.pop()
            self.redo_stack.append(current_state)
            previous_state = self.undo_stack[-1]
            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(1.0, previous_state)
            self.is_modified = True

    def redo(self):
        if self.redo_stack:
            next_state = self.redo_stack.pop()
            self.undo_stack.append(next_state)
            self.text_area.delete(1.0, tk.END)
            self.text_area.insert(1.0, next_state)
            self.is_modified = True

    def cut(self):
        try:
            selected_text = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
            self.add_to_clipboard_history(selected_text)
            self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
        except tk.TclError:
            pass

    def copy(self):
        try:
            selected_text = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
            self.add_to_clipboard_history(selected_text)
            self.root.clipboard_clear()
            self.root.clipboard_append(selected_text)
        except tk.TclError:
            pass

    def paste(self):
        try:
            clipboard_text = self.root.clipboard_get()
            self.add_to_clipboard_history(clipboard_text)
            self.text_area.insert(tk.INSERT, clipboard_text)
        except tk.TclError:
            pass

    def add_to_clipboard_history(self, text):
        if text and text not in self.clipboard_history:
            self.clipboard_history.append(text)
            if len(self.clipboard_history) > 10:
                self.clipboard_history.pop(0)

    def select_all(self):
        self.text_area.tag_add(tk.SEL, "1.0", tk.END)
        self.text_area.mark_set(tk.INSERT, "1.0")
        self.text_area.see(tk.INSERT)

    def find_text(self):
        find_window = tk.Toplevel(self.root)
        find_window.title("查找")
        find_window.geometry("350x150")
        find_window.transient(self.root)
        find_window.grab_set()

        ttk.Label(find_window, text="查找内容:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        find_entry = ttk.Entry(find_window, width=30)
        find_entry.grid(row=0, column=1, padx=5, pady=5)

        case_var = tk.BooleanVar()
        ttk.Checkbutton(find_window, text="区分大小写", variable=case_var).grid(row=1, column=1, sticky=tk.W)

        def do_find():
            search_text = find_entry.get()
            if not search_text:
                return
            start = self.text_area.search(search_text, "1.0", tk.END, nocase=not case_var.get())
            if start:
                end = f"{start}+{len(search_text)}c"
                self.text_area.tag_remove("found", "1.0", tk.END)
                self.text_area.tag_add("found", start, end)
                self.text_area.tag_config("found", background="yellow")
                self.text_area.see(start)
                self.status_label.config(text="找到匹配项")
            else:
                self.status_label.config(text="未找到")

        ttk.Button(find_window, text="查找", command=do_find).grid(row=2, column=0, columnspan=2, pady=10)
        find_entry.focus()

    def make_bold(self, event=None):
        try:
            if "bold" in self.text_area.tag_names(tk.SEL_FIRST):
                self.text_area.tag_remove("bold", tk.SEL_FIRST, tk.SEL_LAST)
            else:
                self.text_area.tag_add("bold", tk.SEL_FIRST, tk.SEL_LAST)
        except tk.TclError:
            pass

    def make_italic(self, event=None):
        try:
            if "italic" in self.text_area.tag_names(tk.SEL_FIRST):
                self.text_area.tag_remove("italic", tk.SEL_FIRST, tk.SEL_LAST)
            else:
                self.text_area.tag_add("italic", tk.SEL_FIRST, tk.SEL_LAST)
        except tk.TclError:
            pass

    def make_underline(self, event=None):
        try:
            if "underline" in self.text_area.tag_names(tk.SEL_FIRST):
                self.text_area.tag_remove("underline", tk.SEL_FIRST, tk.SEL_LAST)
            else:
                self.text_area.tag_add("underline", tk.SEL_FIRST, tk.SEL_LAST)
        except tk.TclError:
            pass

    def preview_html(self):
        content = self.text_area.get(1.0, tk.END)
        try:
            html_content = markdown.markdown(content)
        except:
            html_content = f"<pre>{content}</pre>"
        full_html = f"""<!DOCTYPE html><html><head><meta charset="utf-8"><title>预览</title>
        <style>body{{font-family:Arial;margin:20px}}pre{{background:#f5f5f5;padding:10px}}</style>
        </head><body>{html_content}</body></html>"""
        with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f:
            f.write(full_html)
            temp_file = f.name
        webbrowser.open(f'file://{temp_file}')

    def add_to_recent_files(self, file_path):
        if file_path in self.recent_files:
            self.recent_files.remove(file_path)
        self.recent_files.insert(0, file_path)
        if len(self.recent_files) > 10:
            self.recent_files = self.recent_files[:10]
        self.save_recent_files()

    def load_recent_files(self):
        try:
            if os.path.exists('recent_files.json'):
                with open('recent_files.json', 'r', encoding='utf-8') as f:
                    self.recent_files = json.load(f)
        except:
            self.recent_files = []

    def save_recent_files(self):
        try:
            with open('recent_files.json', 'w', encoding='utf-8') as f:
                json.dump(self.recent_files, f, ensure_ascii=False, indent=2)
        except:
            pass

    def update_recent_files_menu(self):
        pass

    def start_auto_save(self):
        def auto_save():
            if self.is_modified and self.current_file:
                self.save_file()
            self.auto_save_job = self.root.after(30000, auto_save)
        self.auto_save_job = self.root.after(30000, auto_save)


def main():
    root = tk.Tk()
    app = NoteApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()